/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * Description: Hmfs-tools common
 */

#include "hmfs_common.h"

#include <algorithm>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <fcntl.h>
#include <fstream>
#include <libgen.h>
#include <linux/limits.h>
#include <mntent.h>
#include <sstream>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <unordered_map>

#ifdef _WIN32
#include "windows.h"
#include "winioctl.h"
#endif

#include "hmfs_encoding.h"
#include "hmfs_fs.h"
#include "hmfs_io.h"
#include "hmfs_log.h"
#include "hmfs_zoned.h"
#include "securec.h"

namespace OHOS {
namespace Hmfs {
#if defined(__linux__) && defined(_IO) && !defined(BLKGETSIZE)
#define BLKGETSIZE _IO(0x12, 96)
#endif

#if defined(__linux__) && defined(_IOR) && !defined(BLKGETSIZE64)
#define BLKGETSIZE64 _IOR(0x12, 114, size_t)
#endif

#if defined(__linux__) && defined(_IO) && !defined(BLKSSZGET)
#define BLKSSZGET _IO(0x12, 104)
#endif

#ifndef _WIN32
#define O_BINARY 0
#else
#define wchar_t int
#endif

#ifdef _WIN32
#if (_WIN32_WINNT >= 0x0500)
#define HAVE_GET_FILE_SIZE_EX 1
#endif
#endif

inline uint8_t BitmapFirstByteMask(uint64_t start)
{
    return (0xff << static_cast<uint8_t>((start & (BITS_PER_BYTE - 1))));
}

static const int32_t BITS_ARRAY[BITS_IN_BYTE_ARRAY_SIZE] = {
    0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
    3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8,
};

HmfsCommon &HmfsCommon::GetInstance()
{
    static HmfsCommon instance(CmdParser::GetSingleton().GetCmdConfig());
    return instance;
}

int32_t HmfsCommon::LogBase2(uint32_t num)
{
    if (num <= 0 || (num & (num - 1)) != 0) {
        return -1;
    }

    int ret = 0;
    while (num >>= 1) {
        ret++;
    }
    return ret;
}

int32_t HmfsCommon::GetBitsInByte(unsigned char num)
{
    if (num > UINT8_MAX) {
        return -1;
    }
    return BITS_ARRAY[num];
}

int32_t HmfsCommon::TestAndSetBitLe(uint32_t num, uint8_t *addr)
{
    if (addr == nullptr) {
        return -1;
    }

    addr += num >> NUM_SHIFT;
    int mask = 1 << ((num & 0x07));
    int resVal = mask & *addr;
    *addr |= mask;
    return resVal;
}

int32_t HmfsCommon::TestAndClearBitLe(uint32_t num, uint8_t *addr)
{
    if (addr == nullptr) {
        return -1;
    }
    addr += num >> NUM_SHIFT;
    int mask = 1 << ((num & NUM_MASK));
    int resVal = mask & *addr;
    *addr &= ~mask;
    return resVal;
}

int32_t HmfsCommon::TestBitLe(uint32_t num, const uint8_t *addr)
{
    if (addr == nullptr) {
        return -1;
    }
    return ((1 << (num & NUM_MASK)) & (addr[num >> NUM_SHIFT]));
}

int32_t HmfsCommon::VerifyBit(uint32_t num, const uint8_t *ch)
{
    if (ch == nullptr) {
        return -1;
    }
    uint8_t *addr = const_cast<uint8_t *>(ch);
    addr += (num >> NUM_SHIFT);
    int32_t mask = 1 << (NUM_MASK - (num & NUM_MASK));
    return (mask & *addr) != 0;
}

int32_t HmfsCommon::SetBit(uint32_t num, uint8_t *addr)
{
    if (addr == nullptr) {
        return -1;
    }
    addr += (num >> NUM_SHIFT);
    int32_t mask = 1 << (NUM_MASK - (num & NUM_MASK));
    int32_t ret = mask & *addr;
    *addr |= mask;
    return ret;
}

int32_t HmfsCommon::ClearBit(uint32_t num, uint8_t *addr)
{
    if (addr == nullptr) {
        return -1;
    }
    addr += (num >> NUM_SHIFT);
    int32_t mask = 1 << (NUM_MASK - (num & NUM_MASK));
    int32_t ret = mask & *addr;
    *addr &= ~mask;
    return ret;
}

uint64_t HmfsCommon::FindFirstSet(uint8_t word)
{
    uint64_t num = 0;
    if ((word & 0xf) == 0) {
        num += NUM_OFFSET;
        word >>= NUM_OFFSET;
    }
    if ((word & 0x3) == 0) {
        num += VAR_NUM;
        word >>= VAR_NUM;
    }
    if ((word & 0x1) == 0) {
        num += 1;
    }
    return num;
}

uint64_t HmfsCommon::FindNextBitLeFunc(const uint8_t *addr, uint64_t size, uint64_t start, char invert)
{
    if (size == 0 || start >= size || addr == nullptr) {
        return size;
    }

    uint8_t tmp = addr[start / BITS_PER_BYTE] ^ invert;
    tmp &= BitmapFirstByteMask(start);
    start = RoundDown(start, BITS_PER_BYTE);

    while (tmp == 0) {
        start += BITS_PER_BYTE;
        if (start >= size) {
            return size;
        }

        tmp = addr[start / BITS_PER_BYTE] ^ invert;
    }

    return std::min(start + FindFirstSet(tmp), size);
}

uint64_t HmfsCommon::FindNextBitLe(const uint8_t *addr, uint64_t size, uint64_t offset)
{
    return FindNextBitLeFunc(addr, size, offset, 0);
}

uint64_t HmfsCommon::FindNextZeroBitL(const uint8_t *addr, uint64_t size, uint64_t offset)
{
    return FindNextBitLeFunc(addr, size, offset, 0xff);
}

bool HmfsCommon::HasExtraIsize(HmfsInode *inode)
{
    if (inode == nullptr) {
        return false;
    }
    return (inode->inlineFlags & static_cast<uint8_t>(HmfsExtType::HMFS_EXTRA_ATTR));
}

int32_t HmfsCommon::GetExtraIsize(HmfsInode *inode)
{
    if (inode == nullptr) {
        return 0;
    }
    if (HasExtraIsize(inode)) {
        return LE16ToNative(inode->extraInodeSize) / sizeof(uint32_t);
    }
    return 0;
}

int32_t HmfsCommon::GetInlineXattrAddrs(HmfsInode *inode)
{
    if (inode == nullptr) {
        return 0;
    }
    if (cfgPara_.features & HMFS_FEATURE_FLEXIBLE_INLINE_XATTR) {
        return LE16ToNative(inode->inlineXattrSize);
    } else if (inode->inlineFlags & static_cast<uint8_t>(HmfsExtType::HMFS_INLINE_XATTR) ||
        inode->inlineFlags & static_cast<uint8_t>(HmfsExtType::HMFS_INLINE_DENTRY)) {
        return DEFAULT_INLINE_XATTR_ADDRS;
    } else {
        return 0;
    }
}

uint32_t HmfsCommon::AddrsPerInode(HmfsInode *inode)
{
    if (inode == nullptr) {
        return 0;
    }
    uint32_t addrs = CurAddrsPerInode(inode) - GetInlineXattrAddrs(inode);

    if (!LinuxIsReg(LE16ToNative(inode->fileMode)) ||
        (LE32ToNative(inode->fileAttributes) & HMFS_COMPR_FL) == 0) {
        return addrs;
    }
    return AlignDown(addrs, 1 << inode->logClusterSize);
}

uint32_t HmfsCommon::AddrsPerBlock(HmfsInode *inode)
{
    if (inode == nullptr) {
        return 0;
    }
    if (!LinuxIsReg(LE16ToNative(inode->fileMode)) ||
        (LE32ToNative(inode->fileAttributes) & HMFS_COMPR_FL) == 0) {
        return DEF_ADDRS_PER_BLOCK;
    }
    return AlignDown(DEF_ADDRS_PER_BLOCK, 1 << inode->logClusterSize);
}

uint32_t HmfsCommon::GetMaxFileOffset(HmfsInode *inode)
{
    if (!LinuxIsReg(LE16ToNative(inode->fileMode)) ||
        (LE32ToNative(inode->fileAttributes) & HMFS_COMPR_FL) == 0) {
        return LE64ToNative(inode->fileSize);
    }
    return AlignUp(LE64ToNative(inode->fileSize), 1 << inode->logClusterSize);
}

uint32_t HmfsCommon::CalculateCrc32(uint32_t crc, void *buf, size_t len)
{
    if (buf == nullptr) {
        return 0;
    }
    unsigned char *pos = static_cast<unsigned char *>(buf);
    constexpr uint32_t crcPoly = 0xedb88320;
    while (len--) {
        crc ^= *pos++;
        for (uint8_t i = 0; i < CRC_BITS; i++) {
            crc = (crc >> 1) ^ ((crc & 1) ? crcPoly : 0);
        }
    }
    return crc;
}

int32_t HmfsCommon::CheckCrcValid(uint32_t blkCrc, void *buf, size_t len)
{
    if (buf == nullptr) {
        return -1;
    }
    uint32_t calCrc = CalculateCrc32(HMFS_MAGIC_NUMBER, buf, len);
    if (calCrc != blkCrc) {
        HMFS_DEBUG("CRC validation failed: calCrc : %u, blkCrc : %u len : %zu\n", calCrc, blkCrc, len);
        return -1;
    }
    return 0;
}

uint32_t HmfsCommon::GetInodeChksum(NodeData *node, uint32_t crcSeed)
{
    if (node == nullptr) {
        return 0;
    }
    HmfsInode *inode = &node->inode;
    uint32_t inodeNum = node->footer.inodeNumber;
    uint32_t generation = inode->fileVersion;
    uint32_t dummyCs = 0;
    uint32_t offset = offsetof(HmfsInode, inodeChecksum);
    uint32_t csSize = sizeof(dummyCs);

    uint32_t checkSum = CalculateCrc32(crcSeed, reinterpret_cast<uint8_t *>(&inodeNum), sizeof(inodeNum));
    uint32_t checksumSeed = CalculateCrc32(checkSum, reinterpret_cast<uint8_t *>(&generation), sizeof(generation));

    checkSum = CalculateCrc32(checksumSeed, reinterpret_cast<uint8_t *>(inode), offset);
    checkSum = CalculateCrc32(checkSum, reinterpret_cast<uint8_t *>(&dummyCs), csSize);
    offset += csSize;
    checkSum = CalculateCrc32(checkSum, reinterpret_cast<uint8_t *>(inode) + offset, HMFS_BLOCK_SIZE - offset);
    return checkSum;
}

uint32_t HmfsCommon::GetCheckpointChksum(CheckPointData *checkPoint)
{
    if (checkPoint == nullptr) {
        return 0;
    }
    uint32_t checksumOffset = LE32ToNative(checkPoint->checksumOffset);
    uint32_t checkSum = CalculateCrc32(HMFS_MAGIC_NUMBER, checkPoint, checksumOffset);
    if (checksumOffset < CP_CHECKSUM_OFFSET) {
        checksumOffset += sizeof(checkSum);
        checkSum = CalculateCrc32(checkSum, reinterpret_cast<uint8_t *>(checkPoint) + checksumOffset,
            HMFS_BLOCK_SIZE - checksumOffset);
    }
    return checkSum;
}

uint32_t HmfsCommon::CalcExtraIsize(void)
{
    uint32_t size = offsetof(HmfsInode, projectId);
    if (cfgPara_.features & HMFS_FEATURE_FLEXIBLE_INLINE_XATTR) {
        size = offsetof(HmfsInode, projectId);
    }
    if (cfgPara_.features & HMFS_FEATURE_PRJQUOTA) {
        size = offsetof(HmfsInode, inodeChecksum);
    }
    if (cfgPara_.features & HMFS_FEATURE_INODE_CHKSUM) {
        size = offsetof(HmfsInode, creationTime);
    }
    if (cfgPara_.features & HMFS_FEATURE_INODE_CRTIME) {
        size = offsetof(HmfsInode, compressedBlockCount);
    }
    if (cfgPara_.features & HMFS_FEATURE_COMPRESSION) {
        size = offsetof(HmfsInode, extraEnd);
    }
    return size - HMFS_EXTRA_ISIZE_OFFSET;
}

void HmfsCommon::InitQfInode(NodeData *rawNode, uint32_t inodeId, time_t mtime)
{
    if (rawNode == nullptr) {
        return;
    }

    SetLeValue(rawNode->footer.nodeId, inodeId);
    rawNode->footer.inodeNumber = rawNode->footer.nodeId;
    rawNode->footer.checkPointVersion = NativeToLE64(1);
    rawNode->inode.fileMode = NativeToLE16(S_IFREG | S_IRUSR | S_IWUSR);
    rawNode->inode.linkCount = NativeToLE32(1);
    rawNode->inode.userId = NativeToLE32(cfgPara_.rootUid);
    rawNode->inode.groupId = NativeToLE32(cfgPara_.rootGid);
    rawNode->inode.fileSize = NativeToLE64(INODE_FILESIZE);
    rawNode->inode.blockSize = NativeToLE64(1);
    rawNode->inode.accessTime = NativeToLE32(mtime);
    rawNode->inode.accessTimeNsec = 0;
    rawNode->inode.changeTime = NativeToLE32(mtime);
    rawNode->inode.changeTimeNsec = 0;
    rawNode->inode.modificationTime = NativeToLE32(mtime);
    rawNode->inode.modificationTimeNsec = 0;
    rawNode->inode.fileVersion = 0;
    rawNode->inode.xattrNodeId = 0;
    rawNode->inode.fileAttributes = FS_IMMUTABLE_FL;
    rawNode->inode.directoryDepth = NativeToLE32(0);
    rawNode->inode.directoryLevel = 0;

    if (cfgPara_.features & HMFS_FEATURE_EXTRA_ATTR) {
        rawNode->inode.inlineFlags = static_cast<uint8_t>(HmfsExtType::HMFS_EXTRA_ATTR);
        rawNode->inode.extraInodeSize = NativeToLE16(CalcExtraIsize());
    }

    if (cfgPara_.features & HMFS_FEATURE_PRJQUOTA) {
        rawNode->inode.projectId = NativeToLE32(DEFAULT_PROJECT_ID);
    }

    rawNode->inode.largestExtent.fileOffset = 0;
    rawNode->inode.largestExtent.blkAddr = 0;
    rawNode->inode.largestExtent.len = 0;
}

int32_t HmfsCommon::WriteInode(NodeData *inode, uint64_t blkaddr, uint32_t crcSeed)
{
    if (inode == nullptr) {
        return -1;
    }
    if (cfgPara_.features & HMFS_FEATURE_INODE_CHKSUM) {
        inode->inode.inodeChecksum = NativeToLE32(GetInodeChksum(inode, crcSeed));
    }
    return HmfsIo::GetInstance().DevWriteBlock(inode, blkaddr);
}

int32_t HmfsCommon::ReadKernelVersionFromDev(const std::string &path, char *buf, size_t len)
{
    std::ifstream file(path, std::ifstream::in);
    if (!file || buf == nullptr) {
        HMFS_ERROR("Path is not exist or buf is null");
        return -1;
    }

    file.read(buf, len);
    for (size_t i = 0; i < len; i++) {
        if (buf[i] == '\n') {
            buf[i] = 0;
            break;
        }
    }

    return 0;
}

int32_t HmfsCommon::ReadKernelVersion(char *version, size_t len)
{
    if (version == nullptr || len <= VERSION_TIMESTAMP_LEN) {
        return -1;
    }
    size_t copyLen = len - VERSION_TIMESTAMP_LEN;

    if (ReadKernelVersionFromDev("/proc/version", version, len) == 0) {
        return 0;
    }

    struct utsname buf;
    if (uname(&buf)) {
        HMFS_ERROR("Failed to get uname.");
        return -1;
    }
    return snprintf_s(version, len, copyLen, "%s", buf.release);
}

void HmfsCommon::GetKernelVersion(uint8_t *version)
{
    if (version == nullptr) {
        return;
    }
    uint32_t i = 0;
    for (i = 0; i < VERSION_STRING_LEN; i++) {
        if (version[i] == '\n') {
            break;
        }
    }
    if (memset_s(version + i, VERSION_TOTAL_LEN + 1 - i, 0, VERSION_TOTAL_LEN + 1 - i) != EOK) {
        HMFS_ERROR("Failed to set the version using memset.");
    }
}

bool HmfsCommon::KernelVersionSupportQuota()
{
    const uint32_t minMajor = 4;
    const uint32_t minMinor = 14;
    uint32_t major = 0;
    uint32_t minor = 0;
    struct utsname uts;

    if ((uname(&uts) != 0) || (sscanf_s(uts.release, "%u.%u", &major, &minor) != VAR_NUM)) {
        return false;
    }

    if (major > minMajor) {
        return true;
    }

    if (major == minMajor && minor >= minMinor) {
        return true;
    }
    return false;
}

#ifdef __linux__
int32_t HmfsCommon::IsPowerOf2(unsigned long n)
{
    return (n != 0 && ((n & (n - 1)) == 0));
}
#endif

std::vector<std::string> HmfsCommon::SplitStringList(const std::string &srcString, char delimiter)
{
    std::vector<std::string> list;
    std::stringstream strStream(srcString);
    std::string subString;
    while (std::getline(strStream, subString, delimiter)) {
        size_t start = subString.find_first_not_of(" ");
        if (start == std::string::npos) {
            continue;
        }
        size_t end = subString.find_last_not_of(" ");
        list.emplace_back(subString.substr(start, end - start + 1));
    }
    return list;
}

double HmfsCommon::GetBestOverProvision(SuperBlockData *superBlock)
{
    if (superBlock == nullptr) {
        return 0.0;
    }
    double reserved = 0;
    double overprovision = 0;
    double candidate = 0;
    double end = 0;
    double diff = 0;
    double space = 0;
    double maxOvp = 0;
    double maxSpace = 0;
    uint32_t usableSegs = HmfsZoned::GetInstance().HmfsGetUsableSegments(superBlock);

    if (GetLeValue(superBlock->segmentCountInMain) < SEGMENT_MAX_COUNT) {
        candidate = CASE1_CANDIDATE;
        end = CASE1_END;
        diff = CASE1_DIFF;
    } else {
        candidate = CASE2_CANDIDATE;
        end = CASE2_END;
        diff = CASE2_DIFF;
    }

    for (; candidate <= end; candidate += diff) {
        reserved = (CANDIDATE_COEFFICIENT * (PERCENT_TIMES / candidate + 1) + CANDIDATE_ADDNUM) *
            RountUp(usableSegs, GetLeValue(superBlock->sectionCount));
        overprovision = (usableSegs - reserved) * candidate / PERCENT_TIMES;
        space = usableSegs - reserved - overprovision;
        if (maxSpace < space) {
            maxSpace = space;
            maxOvp = candidate;
        }
    }
    return maxOvp;
}

#define DELTA 0x9E3779B9
static void TeaTransform(uint32_t buf[4], uint32_t const inputData[])
{
    if (buf == nullptr || inputData == nullptr) {
        return;
    }
    uint32_t sum = 0;
    uint32_t b0 = buf[BUF_INDEX0];
    uint32_t b1 = buf[BUF_INDEX1];
    uint32_t a = inputData[BUF_INDEX0];
    uint32_t b = inputData[BUF_INDEX1];
    uint32_t c = inputData[BUF_INDEX2];
    uint32_t d = inputData[BUF_INDEX3];
    int num = 16;

    do {
        sum += DELTA;
        b0 += ((b1 << NUM_OFFSET) + a) ^ (b1 + sum) ^ ((b1 >> BUF_SHIFT) + b);
        b1 += ((b0 << NUM_OFFSET) + c) ^ (b0 + sum) ^ ((b0 >> BUF_SHIFT) + d);
    } while (--num);

    buf[BUF_INDEX0] += b0;
    buf[BUF_INDEX1] += b1;
}

static void Str2Hashbuf(const unsigned char *msg, int len, uint32_t *buf, int num)
{
    if (msg == nullptr || buf == nullptr) {
        return;
    }
    unsigned pad = static_cast<uint32_t>(len) | (static_cast<uint32_t>(len) << VALUE_SHIFT);
    pad |= pad << PAD_SHIFT;

    unsigned val = pad;
    if (len > num * NUM_COEFFICIENT) {
        len = num * NUM_COEFFICIENT;
    }

    for (int i = 0; i < len; i++) {
        if ((i % NUM_COEFFICIENT) == 0) {
            val = pad;
        }
        val = msg[i] + (val << VALUE_SHIFT);
        if ((i % NUM_COEFFICIENT) == NUM_SHIFT) {
            *buf++ = val;
            val = pad;
            num--;
        }
    }
    if (--num >= 0) {
        *buf++ = val;
    }
    while (--num >= 0) {
        *buf++ = pad;
    }
}

static uint32_t ExecDentryHashFunc(const unsigned char *name, int len)
{
    if (name == nullptr || ((len <= DENTRIES_MAX_LEN) && (name[0] == '.') &&
        (name[1] == '.' || name[1] == '\0'))) {
        return 0;
    }
    uint32_t hash = 0;
    uint32_t hashRes = 0;
    constexpr uint32_t inputDataSize = 8;
    constexpr uint32_t bufSize = 4;
    uint32_t inputData[inputDataSize];
    uint32_t buf[bufSize];
    buf[BUF_INDEX0] = SEED1;
    buf[BUF_INDEX1] = SEED2;
    buf[BUF_INDEX2] = SEED3;
    buf[BUF_INDEX3] = SEED4;
    const unsigned char *pos = name;
    do {
        Str2Hashbuf(pos, len, inputData, HASH_MAX_LEN);
        TeaTransform(buf, inputData);
        pos += POS_STEP;
        len -= POS_STEP;
    } while (len > POS_STEP);
    hash = buf[0];

    hashRes = NativeToLE32(hash & ~HMFS_HASH_COL_BIT);
    return hashRes;
}

uint32_t HmfsCommon::ExecDentryHash(int32_t encoding, int32_t casefolded, const unsigned char *name, size_t len)
{
    bool isNeedEncoding = IsNeedEncoding(encoding);
    if (!isNeedEncoding) {
        return ExecDentryHashFunc(name, len);
    }

    if (len != 0 && casefolded != 0) {
        auto buff = std::make_unique<unsigned char[]>(HMFS_NAME_LEN);
        int dataLen = CaseFold(name, len, buff.get(), HMFS_NAME_LEN);
        if (dataLen <= 0) {
            return ExecDentryHashFunc(name, len);
        }
        return ExecDentryHashFunc(buff.get(), dataLen);
    }
    return ExecDentryHashFunc(name, len);
}

} // namespace Hmfs
} // namespace OHOS